#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "segger.h"
#include "charset.h"
#include "mylog.h"

const unsigned int SLOT_SIZE[] = {sizeof(struct tok), sizeof(struct vertex), sizeof(struct edge), sizeof(struct role), 0};

segger * segger_new(dat * datp, ctx * ctxp, ctx * nrp, ctx * nsp, ctx * trp)
{
    if(datp == NULL || ctxp == NULL){
        return NULL;
    }
	LOGIT(LV_LOG, "segger new ...");
	segger * p = (segger *)malloc(sizeof(struct segger));
	if(p == NULL){
		return NULL;
	}
	p->datp = datp;

	p->buf = NULL;			//wait for input
	p->len = 0;
	p->off = 0;
	p->slabp = slab_create();
    if(p->slabp == NULL){
		LOGIT(LV_ERROR, "slab new");
		goto ERR;
    }
	int i = 0;
	while(SLOT_SIZE[i]){
		if(slab_add(p->slabp, SLOT_SIZE[i], i) < 0){
			LOGIT(LV_ERROR, "slab add");
			goto ERR;
		}
		i ++;
	}	
	p->resp = result_new(p->slabp);
	if(p->resp == NULL){
		LOGIT(LV_ERROR, "model new");
		goto ERR;
	}
	p->modp = mod_new(p->slabp, datp, p->resp, ctxp, nrp, nsp, trp);
	if(p->modp == NULL){
		LOGIT(LV_ERROR, "model new");
		goto ERR;
    }
	return p;
ERR:
	slab_destroy(p->slabp);
	result_free(p->resp);
	mod_free(p->modp);
	p->datp = NULL;
	p->slabp = NULL;
	p->modp = NULL;
	p->resp = NULL;
	free(p);
	return NULL;
}

int segger_free(segger *p)
{
	if(p == NULL){
		return -1;
	}
	slab_destroy(p->slabp);
	mod_free(p->modp);
	result_free(p->resp);

	p->buf = NULL;
	p->len = 0;
	p->off = 0;
	p->datp = NULL;
	p->slabp = NULL;
	p->modp = NULL;
	p->resp = NULL;
	free(p);
	LOGIT(LV_LOG, "segger free ...");
	return 0;
}

#define RECORD(pushoff, off)	\
				do{				\
					if((pushoff) < (off)){	\
						pushoff = (off);	\
					}			\
				}while(0)

//only accept utf8 C-string
int segger_do(segger * p, const char * input, unsigned int len)
{
	if(p == NULL || input == NULL || len == 0 || len > SEG_LEN_MAX ){
		return SEG_INPUT_ERR;
	}
	p->buf = input;
	p->len = len;
	p->off = 0;					//offset of buffer
	result_clear(p->resp);
	
	int ret = 0;
	unsigned short code = 0;
	unsigned char st = _u_;	//state of cur char
	unsigned char st_ = _u_;//state of next char
	unsigned char _st = _u_;//state of prev char only for unknown push
	int clen = 0;			//len of cur char
	int clen_ = 0;			//len of next char
	int coff_ = 0;			//next char off from this sentence
	int unoff = -1;			//unknown off from this sentence
	int pushoff = -2;		//pushed mm offset from this sentence
	int split_ready = 0;
	dat_res dres;			//dat result

/*
* |buf     |off              |coff_
*  ----------------------------------------------
* |   ...  |     |     | len |len_ |     |  ...  |
*  --------^-----------------^-------------------
*   tokened|           readed|
*
*/
	//prepare
	clen_ = utf8decode(p->buf, &code);
	if(clen_ < 0){
		return SEG_DECODE_ERR;
	}else if(clen_ == 0){
		return SEG_EMPTY;
	}
	st_ = ctag(code);
	_st = st_;
	while(clen_){	//untile next clen = 0 -> '\0';
		st = st_;
		clen = clen_;
		coff_ += clen;//next c off
		if(coff_ + p->off > p->len){
			break;
		}
		clen_ = utf8decode(p->buf + p->off + coff_, &code);
		if(clen_ < 0){
			return SEG_DECODE_ERR;
		}
		st_ = ctag(code);

		switch(st){
		case _z_:
		case _n_:
		case _f_:
			if(pushoff > 0 && unoff != pushoff && coff_ - clen > pushoff){
				ret = mod_push(p->modp, pushoff, coff_ - clen - pushoff,
						st & _m_ ? UN_NUM : UN_STR, PUSH_UNKNOWN);
				if(ret < 0){
					return SEG_PUSHMM_ERR + ret;
				}
				RECORD(pushoff, coff_);
				LOGIT(LV_DEBUG, "+[%u:%u]", pushoff, coff_ - pushoff);
			}

			ret = dat_fetch(p->datp, p->buf + p->off + coff_ - clen, -1, &dres);
			if(ret < 0){
				return SEG_DATFETCH_ERR + ret;
			}
			if(dres.num == 0 && (st == _z_ || st & _m_)){
				ret  = mod_push(p->modp, coff_ - clen, clen,
								 0, PUSH_CHAR);
				if(ret < 0){
					return SEG_PUSHMM_ERR + ret;
				}
				RECORD(pushoff, coff_);
				LOGIT(LV_DEBUG, "[%u:%u]", coff_ - clen, clen);
            }

			int i = 0;
			for(i = 0; i < dres.num; i ++){
				assert(dres.item[i].len > 0);
				if(i > 0 && split_ready == 1){
					break;
				}

				ret = mod_push(p->modp, coff_ - clen, dres.item[i].len, 
								dres.item[i].headi, PUSH_TAG);
				if(ret < 0){
					return SEG_PUSHMM_ERR + ret;
				}
				RECORD(pushoff, coff_ - clen + dres.item[i].len);
				LOGIT(LV_DEBUG, "[%u:%u(%u)]", coff_ - clen, dres.item[i].len, dres.item[i].headi);
			}
		case _m_:
		case _e_:
		case _s_:
			if(coff_ > MODEL_SIZE_MAX - DICT_WORD_LEN * 2){
				split_ready = 1;
				if(coff_ >= pushoff){
					goto DO;
				}
			}
			//FIXME: multiple push single chinese nf
			if(st != _z_ && unoff == -1){
				unoff = coff_ - clen;
			}
			//cur is unknown but next is not same type
			if((st_ != st) && unoff != -1){
				ret = mod_push(p->modp, unoff, coff_ - unoff,
							st & _m_ ? UN_NUM : UN_STR, PUSH_UNKNOWN);
				if(ret < 0){
					return SEG_PUSHMM_ERR + ret;
				}
				LOGIT(LV_DEBUG, "-[%u:%u]%d", unoff, coff_ - unoff, pushoff);
				if(pushoff == unoff){
					RECORD(pushoff, coff_);
				}
				unoff = -1;
			}
			if(st_ & (_z_|_m_|_e_|_s_)){
				continue;
			}
DO:
			if(pushoff != -2 && coff_ > pushoff){
				ret = mod_push(p->modp, pushoff, coff_ - pushoff,
						st & _m_ ? UN_NUM : UN_STR, PUSH_UNKNOWN);
				if(ret < 0){
					return SEG_PUSHMM_ERR + ret;
				}
				LOGIT(LV_DEBUG, "=[%u:%u]", pushoff, coff_ - pushoff);
				pushoff = -2;
			}
			mod_set_cnum(p->modp, coff_);
			ret = mod_do(p->modp);//seg time
			if(ret < 0 || (unsigned int)ret != coff_){
				LOGIT(LV_ERROR, "mod do [%u]%d != %u", p->off, ret, coff_);
				return SEG_MMSEG_ERR + ret;
			}
			mod_clear(p->modp);
			p->off += coff_;
			coff_ = 0;
			unoff = -1;
			pushoff = -2;
			split_ready = 0;
			break;
		case _d_:	//st(d)->each->token
			if(result_push(p->resp, clen) < 0){
				return SEG_PUSHTOK_ERR;
			}
			p->off += coff_;
			coff_ = 0;
			pushoff = -2;
			break;
		case _l_:	//st(l)->end->token
			if(st_ & _l_){
				continue;
			}
			if(result_push(p->resp, coff_) < 0){
				return SEG_PUSHTOK_ERR;
			}
			p->off += coff_;//next push pos
			coff_ = 0;
			pushoff = -2;
			break;
		case _u_:	//st(u)->end->token
			if(st_ == _u_){//_u_ is zero !!!
				continue;
			}
			if(result_push(p->resp, coff_) < 0){
				return SEG_PUSHTOK_ERR;
			}
			p->off += coff_;//next push pos
			coff_ = 0;
			pushoff = -2;
			break;
		default:
			break;
		}
		//only continue
	};

	return 0;
}

//return NULL means ending
const char * segger_next_res(segger * p, unsigned int * lenp)
{
	if(p == NULL || lenp == NULL){
		return NULL;
	}
	unsigned int off = 0;
	*lenp = result_next(p->resp, &off);
	if(*lenp == 0){//end
		return NULL;
	}
	return p->buf + off;
}

#ifdef SEGGER_MAIN
int main(int argc, char ** argv)
{
	LOGON("stdout");
	init_codemap();
    dat * datp = dat_new();
    if(datp == NULL){
        return 1;
    }
    int ret = dat_load(datp, "/home/aaron/workspace/hiseg/clasdict/hidict.dat");
    if(ret < 0){
        return 2;
    }
	ctx * ctxp = ctx_new();
    if(datp == NULL){
        return 3;
    }
    ret = ctx_load(ctxp, "/home/aaron/workspace/hiseg/clasdict/unknown.ctx");
    if(ret < 0){
        return 4;
    }
	ctx * nrp = ctx_new();
    if(nrp == NULL){
        return 5;
    }
    ret = ctx_load(nrp, "/home/aaron/workspace/hiseg/clasdict/nr.ctx");
    if(ret < 0){
        return 6;
    }
	ctx * nsp = ctx_new();
    if(datp == NULL){
        return 5;
    }
    ret = ctx_load(nsp, "/home/aaron/workspace/hiseg/clasdict/ns.ctx");
    if(ret < 0){
        return 6;
    }
	ctx * trp = ctx_new();
    if(datp == NULL){
        return 5;
    }
    ret = ctx_load(trp, "/home/aaron/workspace/hiseg/clasdict/tr.ctx");
    if(ret < 0){
        return 6;
    }

	segger * p = segger_new(datp, ctxp, nrp, nsp, trp);
	if( p == NULL ){
		return 7;
	}
	char * s[] = {
					"武阳村",
					"欢迎张小维先生",
					"说的确实在理",
					"明天就不一定了",
					"关于温天奇的传闻",
					"产生100万投资二零零六年轮着12",
				};
	int i = 0;
	for(i=0; i<sizeof(s)/sizeof(char *); i++){
		printf("%s\nstart ... \n", s[i]);
		int ret = segger_do(p, s[i], strlen(s[i]));
		if(ret < 0){
			printf("\n!!!!!ret: %d\n", ret);
			return i+1;
		}
		printf("\n");
		const char * res = NULL;
		unsigned len = 0;
		char out[1024*32] = {0};
		while((res = segger_next_res(p, &len)) != NULL){
			strncpy(out, res, len);
			out[len] = 0;
			printf("%s ", out);
		}
		printf("\n");
	}
	segger_free(p);
	dat_free(datp);
	ctx_free(ctxp);
	ctx_free(nrp);
	free_codemap();
	LOGOFF();
	return 0;
}
#endif
